﻿using System;
using System.Linq;
using Xenta.Entities;
using Xenta.Utils;

namespace Xenta.Operations
{
    /// <summary>
    /// Represents the identifier based entity operation.
    /// </summary>
    public abstract class OperateEntityByID : Operation
    {
        #region Properties

        /// <summary>
        /// Gets or sets an entity identifier.
        /// </summary>
        public int EntityID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(EntityID <= 0)
                return Err.Of<Entity>().NotValid("EntityID");
            return base.Validate(uow);
        }

        #endregion
    }

    /// <summary>
    /// Represents the identifier based entity operation.
    /// </summary>
    /// <typeparam name="TResult">The type of result.</typeparam>
    public abstract class OperateEntityByID<TResult> : Operation<TResult>
    {
        #region Properties

        /// <summary>
        /// Gets or sets an entity identifier.
        /// </summary>
        public int EntityID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(EntityID <= 0)
                return Err.Of<Entity>().NotValid("EntityID");
            return base.Validate(uow);
        }

        #endregion
    }

    /// <summary>
    /// Represents the entity create operation base.
    /// </summary>
    /// <typeparam name="TEntity">The type of entity.</typeparam>
    public abstract class CreateEntity<TEntity> : Operation<TEntity>
        where TEntity : class, IEntity
    {
        protected override void Work(IUnitOfWork uow, out TEntity result)
        {
            var entity = Activator.CreateInstance<TEntity>();
            Construct(uow, entity);
            uow.Create(entity);
            result = entity;
        }

        /// <summary>
        /// Prepares a newly created version of an entity.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <param name="entity">The entity to revise.</param>
        protected virtual void Construct(IUnitOfWork uow, TEntity entity)
        {
        }
    }

    /// <summary>
    /// Represents the entity update operation base.
    /// </summary>
    /// <typeparam name="TEntity">The type of entity.</typeparam>
    public abstract class UpdateEntity<TEntity> : OperateEntityByID<TEntity>
        where TEntity : class, IEntity
    {
        protected override void Work(IUnitOfWork uow, out TEntity result)
        {
            var entity = uow.Execute(new GetEntity<TEntity>
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            Revise(uow, entity);
            uow.Update(entity);
            result = entity;
        }

        /// <summary>
        /// Prepares a newly edited version of an entity.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <param name="entity">The entity to revise.</param>
        protected virtual void Revise(IUnitOfWork uow, TEntity entity)
        {
        }
    }

    /// <summary>
    /// Represents the entity delete operation.
    /// </summary>
    /// <typeparam name="TEntity">The type of entity.</typeparam>
    public class DeleteEntity<TEntity> : OperateEntityByID
        where TEntity : class, IEntity
    {
        protected override void Work(IUnitOfWork uow)
        {
            var entity = uow.Execute(new GetEntity<TEntity>
            {
                EntityID = EntityID
            }).Result;
            if(entity == null)
                return;
            Destruct(uow, entity);
            uow.Delete(entity);
        }

        /// <summary>
        /// Prepares the entity to be deleted.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <param name="entity">The entity to dispose.</param>
        protected virtual void Destruct(IUnitOfWork uow, TEntity entity)
        {
        }
    }

    /// <summary>
    /// Represents the entity get operation.
    /// </summary>
    /// <typeparam name="TEntity">The type of entity.</typeparam>
    public class GetEntity<TEntity> : OperateEntityByID<TEntity>
        where TEntity : class, IEntity
    {
        #region Properties

        /// <summary>
        /// Gets or sets a value indicates, whether the 
        /// operation throws exception if entity not 
        /// found.
        /// </summary>
        public bool ThrowsException
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out TEntity result)
        {
            result = uow.Get<TEntity>(EntityID);
            if(result != null)
                return;
            if(!ThrowsException)
                return;
            throw Err.Of<TEntity>().NotFound().Ex();
        }

        #endregion
    }

    /// <summary>
    /// Represents the entity query operation base.
    /// </summary>
    /// <typeparam name="TEntity">The type of entity.</typeparam>
    public abstract class QueryEntity<TEntity> : QueryOperation<TEntity, TEntity>
        where TEntity : class, IEntity
    {
        #region Properties

        /// <summary>
        /// Gets or sets a value indicates, whether the 
        /// operation throws exception if entity not 
        /// found.
        /// </summary>
        public bool ThrowsException
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override TEntity Do(IUnitOfWork uow, IQueryable<TEntity> query)
        {
            var entity = query.FirstOrDefault();
            if(entity == null && ThrowsException)
                throw Err.Of<TEntity>().NotFound().Ex();
            return entity;
        }

        #endregion
    }

    /// <summary>
    /// Represents the identifier based entity query operation.
    /// </summary>
    /// <typeparam name="TEntity">The type of entity.</typeparam>
    public class QueryEntityByID<TEntity> : QueryEntity<TEntity>
        where TEntity : class, IEntity
    {
        #region Properties

        /// <summary>
        /// Gets or sets an entity identifier.
        /// </summary>
        public int EntityID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(EntityID <= 0)
                return Err.Of<Entity>().NotValid("EntityID");
            return base.Validate(uow);
        }

        protected override IQueryable<TEntity> Build(IUnitOfWork uow, IQueryable<TEntity> query)
        {
            return base.Build(uow, query).Where(x => x.EntityID == EntityID);
        }

        #endregion
    }
}
